﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using RabbitMQ.Client;
using StackExchange.Redis;
using System.Text;
using System.Text.Json;
using WAU.DAL;
using WAU.DTOs.OrderModule;
using WAU.Library.RabbitMQ;
using WAU.Models;
using WAU.Models.CommodityModels;
using WAU.Models.Framework;
using WAU.Models.OrderModels.Enums;
using WAU.Services.Routines;
using WAU.Utilities.Redis;
using Order = WAU.Models.OrderModels.Order;
using RabbitMQChannel = RabbitMQ.Client.IModel;

namespace WAU.Services
{
    /// <summary>
    /// 订单相关服务
    /// </summary>
    public class OrderService
    {
        private static object _lock = new object();

        private ILogger<OrderService> _logger;

        private readonly BaseRepoMysql<CommoditySKU> _repoCommoditySKU;
        private readonly BaseRepoMysql<Order> _repoOrder;

        private readonly RabbitMQChannel _rabbitMQChannel;
        
        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="repoCommoditySKU"></param>
        /// <param name="repoOrder"></param>
        /// <param name="rabbitMQModelLease"></param>
        public OrderService(ILogger<OrderService> logger, 
            BaseRepoMysql<CommoditySKU> repoCommoditySKU, 
            BaseRepoMysql<Order> repoOrder,
            ScopedRabbitMQModelLease rabbitMQModelLease)
        {
            _logger = logger;

            this._repoCommoditySKU = repoCommoditySKU;
            this._repoOrder = repoOrder;
            this._rabbitMQChannel = rabbitMQModelLease.Channel;
        }

        /// <summary>
        /// 创建订单（未判断一人一单，且并发情况下超卖，有问题）
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        public async Task<ExecResult> CreateNewOrderAsync(string currentUserId, CreateNewOrderDto dto)
        {
            // 1. 判断库存
            var skuEntity = await _repoCommoditySKU.FindAsync(a => a.Id == dto.SKUId);
            if (skuEntity == null)
            {
                return ExecResult.Error("商品 SKU 不存在");
            }

            if (skuEntity.Quantity < dto.SKUCount)
            {
                return ExecResult.Error("库存不足");
            }

            // 2.创建订单
            Order order = new Order();
            order.Id = GuidFactory.NewSortedGuid();

            order.UserId = currentUserId;
            order.SKUId = dto.SKUId;
            order.SKUCount = dto.SKUCount;
            order.TotalPrice = skuEntity.Price * dto.SKUCount;

            order.Creator = currentUserId;
            order.CreateTime = DateTime.Now;

            // TODO
            order.ModifyTime = DateTime.Now;

            _repoOrder.Add(order);

            // 3. 扣减库存
            skuEntity.Quantity -= dto.SKUCount;
            _repoCommoditySKU.Update(skuEntity);

            await _repoOrder.SaveChangesAsync();

            return ExecResult.ExecSuccessMsg("订单创建成功");
        }

        /// <summary>
        /// 创建订单（内存锁）
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        public ExecResult CreateNewOrderLocalLock(string currentUserId, CreateNewOrderDto dto)
        {
            lock (_lock)
            {
                // 1. 判断
                var skuEntity = _repoCommoditySKU.Find(a => a.Id == dto.SKUId);
                if (skuEntity == null)
                {
                    return ExecResult.Error("商品 SKU 不存在");
                }

                // 1.1 判断库存
                if (skuEntity.Quantity < dto.SKUCount)
                {
                    return ExecResult.Error("库存不足");
                }

                //// 1.2 判断是否重复购买
                //var existQuery = _repoOrder.QueryNoTracking(a => a.UserId == currentUserId && a.SKUId == dto.SKUId);
                //if (existQuery.Any())
                //{
                //    return ExecResult.Error("只能购买一次");
                //}

                ThreadPool.QueueUserWorkItem((state) =>
                {
                    // 2.创建订单
                    Order order = new Order();
                    order.Id = GuidFactory.NewSortedGuid();

                    order.UserId = currentUserId;
                    order.SKUId = dto.SKUId;
                    order.SKUCount = dto.SKUCount;
                    order.TotalPrice = skuEntity.Price * dto.SKUCount;

                    order.Creator = currentUserId;
                    order.CreateTime = DateTime.Now;

                    // TODO
                    order.ModifyTime = DateTime.Now;

                    using(var scope = GlobalReadOnlyVariables.ServiceProvider.CreateScope())
                    {
                        BaseRepoMysql<Order> repoOrder = scope.ServiceProvider.GetRequiredService<BaseRepoMysql<Order>>();

                        repoOrder.Add(order);
                        repoOrder.SaveChanges();
                    }
                });

                // 3. 扣减库存
                skuEntity.Quantity -= dto.SKUCount;
                _repoCommoditySKU.Update(skuEntity);

                _repoOrder.SaveChanges();

                return ExecResult.ExecSuccessMsg("订单创建成功");
            }
        }

        /// <summary>
        /// 将库存数据写入 Redis
        /// </summary>
        /// <param name="skuId"></param>
        /// <returns></returns>
        public ExecResult SetStockToRedis(string skuId)
        {
            var skuEntity = _repoCommoditySKU.Find(a => a.Id == skuId);
            if (skuEntity == null)
            {
                return ExecResult.Error("商品 SKU 不存在");
            }

            RedisClientService.RedisDB.StringSet($"order:stock:{skuId}", skuEntity.Quantity);
            return ExecResult.ExecSuccessMsg("添加缓存成功");
        }

        /// <summary>
        /// （异步）创建订单（Lua 脚本 + 消息队列）
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        public ExecResult CreateNewOrderRedisLua(string currentUserId, CreateNewOrderDto dto)
        {
            RedisResult redisResult = RedisClientService.OrderJudgeScript.Evaluate(
                RedisClientService.RedisDB, 
                new { 
                    skuId = dto.SKUId, 
                    userId = currentUserId,
                    skuCount = -dto.SKUCount
                });

            int result = (int)redisResult;

            if(result == 1)
            {
                return ExecResult.Error("库存不足");
            }
            else if(result == 2)
            {
                return ExecResult.Error("不能重复下单");
            }

            // 2. 将订单信息发送到消息队列
            OrderMQDTO orderMQDTO = new OrderMQDTO()
            {
                UserId = currentUserId,
                SKUId = dto.SKUId,
                SKUCount = dto.SKUCount,
                CreateTime = DateTime.Now
            };

            string exchangeName = MQConsumeOrderRoutine.ORDER_EXCHANGE_NAME;

            // 声明交换机（如果没有则自动创建）
            _rabbitMQChannel.ExchangeDeclare(exchangeName, ExchangeType.Direct, true, false);

            byte[] body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(orderMQDTO));
            _rabbitMQChannel.BasicPublish(exchangeName, "", null, body);

            return ExecResult.ExecSuccessMsg("订单请求成功");
        }

        /// <summary>
        /// 根据 OrderMQDTO 创建订单
        /// </summary>
        /// <param name="serviceScope"></param>
        /// <param name="dto"></param>
        /// <returns></returns>
        public ExecResult CreateOrderFromMQ(OrderMQDTO orderMQDTO)
        {
            #region 1. 写入数据库

            var skuEntity = _repoCommoditySKU.Find(a => a.Id == orderMQDTO.SKUId);

            Order order = new Order();
            order.Id = GuidFactory.NewSortedGuid();

            order.UserId = orderMQDTO.UserId;
            order.SKUId = orderMQDTO.SKUId;
            order.SKUCount = orderMQDTO.SKUCount;
            order.TotalPrice = skuEntity.Price * orderMQDTO.SKUCount;

            order.Creator = orderMQDTO.UserId;
            order.CreateTime = DateTime.Now;
            order.State = OrderStateEnum.待支付;

            // TODO
            order.ModifyTime = DateTime.Now;

            _repoOrder.Add(order);
            _repoOrder.SaveChanges();

            #endregion

            #region 2. 向延迟队列发送消息，用于订单过期未付款检测

            OrderExpireCheckMQDto expireCheckDto = new OrderExpireCheckMQDto()
            {
                OrderId = order.Id,
                CheckIndex = 0
            };

            ExecResult sendOrderToExpireDelayMQResult = SendOrderToExpireDelayMQ(expireCheckDto);
            if (!sendOrderToExpireDelayMQResult.Success)
            {
                return ExecResult.Error("发送订单到过期检测延迟队列失败");
            }

            #endregion

            return ExecResult.ExecSuccess;
        }

        /// <summary>
        /// 将订单发送到 用于过期检查的延迟队列
        /// </summary>
        /// <param name="expireCheckDto"></param>
        /// <returns></returns>
        private ExecResult SendOrderToExpireDelayMQ(OrderExpireCheckMQDto expireCheckDto)
        {
            string exchangeName = MQConsumeOrderExpireRoutine.ORDER_EXPIRE_EXCHANGE_NAME;
            // 声明延迟交换机的类型（与默认的交换机类型相同）
            Dictionary<string, object> exchangeParamsDic = new Dictionary<string, object>
            {
                { "x-delayed-type", "direct" }
            };

            // 声明延迟交换机（如果没有则自动创建）
            _rabbitMQChannel.ExchangeDeclare(exchangeName, "x-delayed-message", true, false, exchangeParamsDic);

            byte[] body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(expireCheckDto));
            int checkMinutes = MQConsumeOrderExpireRoutine.EXPIRE_CHECK_MINUTES_ARRAY[expireCheckDto.CheckIndex];
            IDictionary<string, object> propertyHeaderDic = new Dictionary<string, object>()
            {
                { "x-delay",  checkMinutes * 1000 * 60}
            };
            IBasicProperties basicProperties = _rabbitMQChannel.CreateBasicProperties();
            basicProperties.Headers = propertyHeaderDic;

            // 使用延迟队列插件时，mandatory 参数无效
            _rabbitMQChannel.BasicPublish(exchangeName, "", false, basicProperties, body);

            return ExecResult.ExecSuccess;
        }

        /// <summary>
        /// 订单是否过期检查
        /// </summary>
        /// <param name="expireCheckMQDto"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public ExecResult ExpireCheck(OrderExpireCheckMQDto expireCheckMQDto)
        {
            Order orderEntity = _repoOrder.Find(a => a.Id == expireCheckMQDto.OrderId);
            if(orderEntity == null)
            {
                return ExecResult.Error("订单不存在");
            }

            // 1. 如果不是待支付，则直接返回
            if(orderEntity.State != OrderStateEnum.待支付)
            {
                return ExecResult.ExecSuccessMsg("订单已支付");
            }

            // 2. 待支付，且未到达 过期检测时间数组 的上限，则进行下一轮检测
            if(expireCheckMQDto.CheckIndex < MQConsumeOrderExpireRoutine.EXPIRE_CHECK_MINUTES_ARRAY.Length - 1)
            {
                expireCheckMQDto.CheckIndex++;
                SendOrderToExpireDelayMQ(expireCheckMQDto);
                return ExecResult.ExecSuccessMsg($"订单 {orderEntity.Id} 进行下一轮检测，当前时间为 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
            }

            // 3. 待支付，且到达了 过期检测时间数组 的上限，则取消订单，回收库存
            orderEntity.State = OrderStateEnum.过期未支付自动取消;
            _repoOrder.Update(orderEntity);

            // TODO 此处应该使用分布式锁，避免多个实例同时进行该操作。或者使用消息队列统一处理
            // TODO 此处应判断往哪加库存（Redis 还是 数据库）
            CommoditySKU skuEntity = _repoCommoditySKU.Find(orderEntity.SKUId);
            skuEntity.Quantity += orderEntity.SKUCount;
            _repoCommoditySKU.Update(skuEntity);

            _repoOrder.SaveChanges();
            return ExecResult.ExecSuccessMsg($"订单 {orderEntity.Id} 超时未支付，已被取消");
        }
    }
}
